﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using NPack;
using NUnit.Framework;

namespace TaskFramework.Tests
{
    [TestFixture]
    public class TaskPoolFixture
    {
        readonly Queue<TaskEvent> _taskEventQueue = new Queue<TaskEvent>();

        private void onTaskEvent(TaskEvent info)
        {
            lock (_taskEventQueue)
            {
                _taskEventQueue.Enqueue(info);
            }
        }

        [TearDown]
        public void Reset()
        {
            lock (_taskEventQueue)
            {
                _taskEventQueue.Clear();
            }
        }

        private int _completedTaskCount;
        private Thread _completedTaskCounterThread;

        private void CompletedTaskCounter()
        {
            try
            {
                while (true)
                {
                    lock (_taskEventQueue)
                    {
                        while (_taskEventQueue.Count > 0)
                        {
                            TaskEvent e = _taskEventQueue.Dequeue();
                            if (e.TaskCompleted)
                            {
                                _completedTaskCount++;
                            }
                        }
                    }

                    Thread.Sleep(10);
                }
            }
            catch (ThreadInterruptedException) { }
        }

        private void StartCompletedTaskCounter()
        {
            _completedTaskCounterThread = new Thread(CompletedTaskCounter);
            _completedTaskCounterThread.Start();
        }

        private void StopCompleterTaskCounter()
        {
            _completedTaskCounterThread.Interrupt();
            _completedTaskCounterThread.Join();
        }

        [Test]
        public void should_execute_a_task_via_a_task_pool()
        {
            ITaskPool pool = new TaskPool();
            pool.WriteDebugInfo = true;
            FetchTradesTask task = new FetchTradesTask(new FetchTradesRequest(1000));

            pool.QueueTask(task, onTaskEvent);

            Debug.WriteLine("Waiting 2 seconds for all tasks to complete.");
            Thread.Sleep(2000);

            TaskEvent info = _taskEventQueue.Dequeue();

            Assert.IsTrue(info.HasResult);
            Assert.IsNull(info.Exception);

            IList<Trade> trades = (IList<Trade>)info.Result;

            Assert.AreEqual(1, trades.Count);
            Assert.AreEqual(873603, trades[0].TradeId);
        }

        [Test]
        public void should_ensure_that_all_queued_tasks_are_run()
        {
            StartCompletedTaskCounter();

            const bool writeDebugInfo = false;

            TaskPool pool = new TaskPool() {WriteDebugInfo = writeDebugInfo};
            ITaskDispatcher dispatcher = new TaskDispatcher(pool) {WriteDebugInfo = writeDebugInfo};
            dispatcher.AddTaskFactory(new QuickTaskFactory());
            dispatcher.TaskEvent += onTaskEvent;

            int taskCount = 500;

            for (int i = 1; i <= taskCount; i++)
            {
                if (i % taskCount == 0)
                {
                    Debug.WriteLine("Routed " + i + " initial requests.");
                }

                int routeCount = _random.Next(0, 3);
                RouteType routeType = GetRandomRouteType();
                const int delayMilliseconds = 0;
                bool shouldThrowBeforeRouting = GetRandomShouldThrowBool();
                bool shouldThrowAfterRouting = (!shouldThrowBeforeRouting) ? GetRandomShouldThrowBool() : false;

                RouteTask(dispatcher, routeCount, routeType, delayMilliseconds, shouldThrowBeforeRouting, shouldThrowAfterRouting);
            }

            Debug.WriteLine("Waiting for " + _expectedCompletedTasks + " tasks to complete.");
            DateTime started = DateTime.Now;

            const double timeoutSeconds = 30;
            DateTime timeoutTime = DateTime.Now.AddSeconds(timeoutSeconds);
            DateTime now = DateTime.MinValue;

            do
            {
                Thread.Sleep(100);
            } while (_completedTaskCount < _expectedCompletedTasks & (now = DateTime.Now) < timeoutTime);

            if (now > timeoutTime)
            {
                Debug.WriteLine("Timed out while waiting for tasks.");
            }
            else
            {
                Debug.WriteLine("Done. Sleeping for 100ms to ensure more tasks don't complete.");
                Thread.Sleep(100);
                Debug.WriteLine("Time taken: " + (now - started).TotalSeconds + " seconds.");
            }

            Assert.AreEqual(_expectedCompletedTasks, _completedTaskCount);
            Debug.WriteLine(_completedTaskCount + " tasks completed successfully.");

            pool.KillAll();
            StopCompleterTaskCounter();
        }

        private void RouteTask(ITaskDispatcher dispatcher, int routeCount, RouteType routeType, int delayMilliseconds, bool shouldThrowBeforeRouting, bool shouldThrowAfterRouting)
        {
            QuickTaskRequest request;
            if (shouldThrowBeforeRouting)
            {
                request = new QuickTaskRequest(true);
            }
            else
            {
                request = new QuickTaskRequest(routeCount, routeType, delayMilliseconds, shouldThrowAfterRouting);
                
                if (!shouldThrowAfterRouting)
                {
                    if (request.RouteType != RouteType.TransferTaskToPool)
                    {
                        _expectedCompletedTasks += routeCount; // all completed routed requests
                    }
                    _expectedCompletedTasks++; // this task will raise a completed event
                }
            }

            dispatcher.RouteRequest(request);
        }

        private int _expectedCompletedTasks;

        [Test]
        public void should_route_a_request_via_a_task_dispatcher()
        {
            ITaskPool taskPool = new TaskPool(16);
            taskPool.WriteDebugInfo = true;
            ITaskDispatcher dispatcher = new TaskDispatcher(taskPool);
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += onTaskEvent;
            dispatcher.WriteDebugInfo = true;

            dispatcher.RouteRequest(new FetchTradesRequest(1000));

            Thread.Sleep(200);

            Assert.AreEqual(2, _taskEventQueue.Count);
            TaskEvent info = _taskEventQueue.Dequeue();

            Assert.IsTrue(info.HasResult);
            Assert.IsNull(info.Exception);
            Assert.AreEqual(0, info.Info.RoutedCount);

            IList<Trade> trades = (IList<Trade>)info.Result;

            Assert.AreEqual(1, trades.Count);
            Assert.AreEqual(873603, trades[0].TradeId);
        }

        [Test]
        public void should_ensure_that_routed_tasks_are_inserted_at_the_correct_queue_position()
        {
            ITaskPool taskPool = new TaskPool(1);
            taskPool.WriteDebugInfo = true;
            ITaskDispatcher dispatcher = new TaskDispatcher(taskPool);
            dispatcher.AddTaskFactory(new QuickTaskFactory());
            dispatcher.TaskEvent += onTaskEvent;
            dispatcher.WriteDebugInfo = true;

            Task task1 = new QuickTask(new QuickTaskRequest(2, RouteType.RouteSingleRequestAndDecrementRouteCount, 50, false));

            taskPool.QueueTask(task1, onTaskEvent);

            dispatcher.RouteRequest(new QuickTaskRequest());

            Thread.Sleep(250);

            Assert.AreEqual(4, _taskEventQueue.Count);

            Assert.AreEqual(task1.TaskId, _taskEventQueue.Dequeue().Info.Task.TaskId);
            Assert.AreEqual(task1.TaskId + 2, _taskEventQueue.Dequeue().Info.Task.TaskId);
            Assert.AreEqual(task1.TaskId + 3, _taskEventQueue.Dequeue().Info.Task.TaskId);
            Assert.AreEqual(task1.TaskId + 1, _taskEventQueue.Dequeue().Info.Task.TaskId);
        }

        [Test]
        public void should_ensure_that_routed_tasks_are_inserted_at_the_correct_queue_position_2()
        {
            ITaskPool taskPool = new TaskPool(1);
            taskPool.WriteDebugInfo = true;
            ITaskDispatcher dispatcher = new TaskDispatcher(taskPool);
            dispatcher.AddTaskFactory(new QuickTaskFactory());
            dispatcher.TaskEvent += onTaskEvent;
            dispatcher.WriteDebugInfo = true;

            Task task1 = new QuickTask(new QuickTaskRequest(2, RouteType.RouteSingleRequestAndDecrementRouteCount, 70, false));
            Task task2 = new QuickTask(new QuickTaskRequest(2, RouteType.RouteSingleRequestAndDecrementRouteCount, 70, false));

            taskPool.QueueTask(task1, onTaskEvent);
            taskPool.QueueTask(task2, onTaskEvent);

            dispatcher.RouteRequest(new QuickTaskRequest());


            Thread.Sleep(650);

            Assert.AreEqual(7, _taskEventQueue.Count);

            Assert.AreEqual(task1.TaskId, _taskEventQueue.Dequeue().Info.Task.TaskId);     // task 1             : TaskId 1 (assuming no previous tasks...)
            TaskInfo info = _taskEventQueue.Dequeue().Info;
            Assert.AreEqual(task1.TaskId + 3, info.Task.TaskId);                           // routed by task 1   : TaskId 4
            Assert.AreEqual(task1.TaskId, info.RoutedByTaskId);
            info = _taskEventQueue.Dequeue().Info;
            Assert.AreEqual(task1.TaskId + 4, info.Task.TaskId);                           // routed by task 1   : TaskId 5
            Assert.AreEqual(task1.TaskId + 3, info.RoutedByTaskId);
            Assert.AreEqual(task2.TaskId, _taskEventQueue.Dequeue().Info.Task.TaskId);     // task 2             : TaskId 2
            info = _taskEventQueue.Dequeue().Info;
            Assert.AreEqual(task2.TaskId + 4, info.Task.TaskId);                           // routed by task 2   : TaskId 6
            Assert.AreEqual(task2.TaskId, info.RoutedByTaskId);
            info = _taskEventQueue.Dequeue().Info;
            Assert.AreEqual(task2.TaskId + 5, info.Task.TaskId);                           // routed by task 2   : TaskId 7
            Assert.AreEqual(task2.TaskId + 4, info.RoutedByTaskId);
            Assert.AreEqual(task1.TaskId + 2, _taskEventQueue.Dequeue().Info.Task.TaskId); // dispatched request : TaskId 3
        }

        [Test]
        public void should_submit_a_task_multiple_times_and_ensure_it_does_not_crash_task_pool()
        {
            ITaskPool pool = new TaskPool();
            pool.WriteDebugInfo = true;

            Task task = new QuickTask(new QuickTaskRequest(0, RouteType.RouteSingleRequestAndDecrementRouteCount, 50, false));

            pool.QueueTask(task, onTaskEvent);
            pool.QueueTask(task, onTaskEvent);

            Thread.Sleep(200);

            Assert.AreEqual(2, _taskEventQueue.Count);
        }

        private readonly MersenneTwister _random = new MersenneTwister();

        private RouteType GetRandomRouteType()
        {
            int magicNumber = _random.Next(0, 1000000);
            if (magicNumber > 999000) return RouteType.TransferTaskToPool;
            if (magicNumber > 500000) return RouteType.RouteRequestInBatchAndSetRouteCountToZero;
            return RouteType.RouteSingleRequestAndDecrementRouteCount;
        }

        private bool GetRandomShouldThrowBool()
        {
            int magicNumber = _random.Next(0, 1000000);
            return magicNumber > 999500;
        }
    }

    
}
